/*
 使用属性描述来定义属性的原因：
 - 为了不让 for...in 等类似的操作遍历这些定义在原型上的方法或属性，需要将属性设置为不可枚举的；
 - 为了解决给 Object.prototype 添加属性会在Vue中报错的问题，需要将属性设置为不可枚举的；
 */


let propertyDescriptors = {


    /**
     * 找出对象中符合测试函数的属性名
     * @param testFun:(propValue,propkey,index)=> boolean  ; 测试条件函数
     */
    findKey: {
        enumerable: false,
        value: function (testFun) {

            let propKey = Object.keys(this).find((key, index) => {
                let propValue = this[key];
                return testFun(propValue, key, index);
            });

            return propKey;
        }
    },


    /**
     * 找出对象中符合测试函数的属性信息
     * @param testFun:(propValue,propkey,index)=> boolean  ; 测试条件函数
     * @return PropertyInfo : {key:string,value:any}
     */
    findProperty: {
        enumerable: false,
        value: function (testFun) {

            let prop = Object.entries(this).find((entry, index) => {
                return testFun(entry[1], entry[0], index);
            });


            let propInfo = {
                key: null,
                value: null
            };

            if (prop) {
                propInfo.key = prop[0];
                propInfo.value = prop[1];
            }

            return propInfo;
        }
    },


    /**
     * 检验该对象自身是否是扁平的，即：该对象的所有的直接属性的属性值都是非对象类型；
     */
    isFlat: {
        enumerable: false,
        get: function () {
            let noFlat = Object.values(this).some(function (propValue) {
                let propType = typeof propValue;
                return propType == "object" || propType == "function";
            });

            return !noFlat;
        }
    },




    /**
     * 返回对象是否是空的对象，即没有自己的可枚举的属性
     */
    noKeys:{
        enumerable:false,
        get:function(){
            return Object.keys(this).length == 0;
        }
    },





    /**
     * 获取对象中拥有的 相应key的值；
     * @param keys:[string]  指定的key的数组
     * @return [any]    对象中拥有的相应key的值
     */
    getValuesOfKeys: {
        enumerable: false,
        value: function (keys) {

            var _this = this ;
            return keys.reduce(function(total, currentKey){
                if (currentKey in _this){
                    total.push(_this[currentKey]);
                }
                return total;
            }, []);

        }
    },



    /**
     * 获取对象中拥有的 相应key的 有效值；
     * 注意：不包含值为 undefined 或 null 的值
     * @param keys:[string]  指定的key的数组
     * @return [any]    对象中拥有的相应key的有效值
     *
     */
    getVirtualValuesOfKeys: {
        enumerable: false,
        value: function (keys) {

            var _this = this ;
            return keys.reduce(function(total, currentKey){
                var currValue = _this[currentKey] ;
                if (currValue != undefined){
                    total.push(currValue);
                }
                return total;
            }, []);

        }
    },





    /**
     * 查找对象中所有指定的属性中的第一个有效值
     * @param keys : [string]   被查找的属性列表
     * @returns any  对象中所有指定的属性中的第一个有效值
     */
    findValueOfKeys: {
        enumerable: false,
        value: function (keys) {
            var findValue ;

            keys.find(function(currentKey){
                var currValue = this[currentKey] ;
                var valid =  currValue != undefined
                if (valid){
                    findValue = currValue ;
                }
                return valid ;

            },this);


            return findValue;

        }
    },



    /**
     * 获取对象中所有指定格式的属性的值列表
     * @param key : string   基本的属性字符串
     * @param formats : [FormatObject]  | FormatObject   格式对象 或者 数组
     * FormatObject := {separator : string, caseType : L | U | N}
     * @property separator  : string     分隔符
     * @property caseType  : L | U | N     大小写类型；   L : 小写，当没有设置 separator 时，将会把所有字符都转为小写 ； U : 大写 ，当没有设置 separator 时，将会把所有字符都转为大写； N : 正常
     * @returns [any]  对象中所有指定格式的属性的值列表
     */
    getValuesForKeyFormats: {
        enumerable: false,
        value: function (key,formats) {
            var keyStrList = key.getAllStrForFormats(formats);
            return this.getValuesOfKeys(keyStrList);
        }
    },


    /**
     * 获取对象中所有指定格式的属性的有效值列表
     * @param key : string   基本的属性字符串
     * @param formats : [FormatObject]  | FormatObject   格式对象 或者 数组
     * FormatObject := {separator : string, caseType : L | U | N}
     * @property separator  : string     分隔符
     * @property caseType  : L | U | N     大小写类型；   L : 小写，当没有设置 separator 时，将会把所有字符都转为小写 ； U : 大写 ，当没有设置 separator 时，将会把所有字符都转为大写； N : 正常
     * @returns [any]  对象中所有指定格式的属性的值列表
     */
    getVirtualValuesForKeyFormats: {
        enumerable: false,
        value: function (key,formats) {
            var keyStrList = key.getAllStrForFormats(formats);
            return this.getVirtualValuesOfKeys(keyStrList);
        }
    },


    /**
     * 查找对象中所有指定格式的属性的第一个有效值
     * @param key : string   基本的属性字符串
     * @param formats : [FormatObject]  | FormatObject   格式对象 或者 数组
     * FormatObject := {separator : string, caseType : L | U | N}
     * @property separator  : string     分隔符
     * @property caseType  : L | U | N     大小写类型；   L : 小写，当没有设置 separator 时，将会把所有字符都转为小写 ； U : 大写 ，当没有设置 separator 时，将会把所有字符都转为大写； N : 正常
     * @returns any  对象中所有指定格式的属性的第一个有效值
     */
    findValueForKeyFormats: {
        enumerable: false,
        value: function (key,formats) {
            var keyStrList = key.getAllStrForFormats(formats);
            return this.findValueOfKeys(keyStrList);
        }
    },





    //集合：开始

    /**
     * 判断当前对象是否是指定对象的子集；即当前对象自己的所有可枚举属性 及 值 是否都包含于 指定的对象上；
     * @param universalObj ? : Object   全集对象
     * @param equalTest ? : (a,b)=>boolean    可选， 默认是通过全等 === 来判断元素是否相等的；测试数组元素是否相同的函数，返回 boolean 值，表示 元素a 和 元素b  相同；
     * @returns boolean   当前对象是否是指定对象的子集
     */
    isSubsetOf: {
        enumerable: false,
        value: function (universalObj,equalTest) {

            if (!equalTest) {
                equalTest = function (a, b) {
                    return a === b;
                };
            }

            let thisEntries = Object.entries(this);

            return thisEntries.every(function(entrie,index){
                let key = entrie[0];
                let value = entrie[1];
                return equalTest.call(this,value,universalObj[key]);
            },this);

        }
    },



    //集合：结束





    //URL相关：开始


    /**
     * toJSONqueryStringify(queryPrefix)
     * 把当前对象 用JSON的方式格式化成 URL 的参数格式
     * @param queryPrefix ? : boolean    可选；默认值：false; 是否带URL的查询字符串前缀 ?
     * @returns 格式化后的 URL 的参数格式
     *
     * 说明
     * 本方法是通过 JSONQueryStringify 方法进行格式化的
     */
    toJSONqueryStringify: {
        enumerable: false,
        value: function (queryPrefix) {
            return JSONQueryStringify(this,queryPrefix);
        }
    },


    //URL相关：结束




    /**
     * depthLoopOwnProperty(callback,depth,all,thisValue,initDepth)=> stopInfo
     * 递归遍历自身属性链中的所有属性
     * @param callback : (key,value,obj,currDepth))=> stopInfo : any    必选； 循环遍历的回调函数； key : 当前被遍历的属性名；value : 当前被遍历的属性值；obj : 当前被遍历的属性所属的对象；currDepth : 当前遍历的深度值，从 initDepth 所表示的值开始计数；返回值 stopInfo : 表示是否中止循环，并且该值会被 depthLoopOwnProperty 函数返回，如果返回的值是真值，则终止循环；
     * @param depth ? : number    可选；默认值：-1 ,即无限深度； 要循环遍历的深度；
     * @param all ? : boolean    可选；默认值: false ;  是否遍历自身所有的属性，包括不可枚举的；
     * @param thisValue ? : any    可选；   callback 回调函数的this值 ；默认值：当前被遍历的属性所属的对象；
     * @param initDepth ? : number   可选；默认值：1；深度的初始值； 注意：设计该属性的主要目的是为了递归调用时记录当前传递当前的深度值的；
     * @returns stopInfo ： any   终止循环时返回的信息；
     */
    depthLoopOwnProperty:{
        enumerable: false,
        value:function (callback,depth,all,thisValue,initDepth) {
            if (depth == undefined){
                depth = -1;
            }

            if (depth == 0){
                return;
            }

            if (initDepth == undefined){
                initDepth = 1;
            }


            if (thisValue === undefined) {
                thisValue = this;
            }

            if (all){
                var keyList = Object.getOwnPropertyNames(this);
            } else {
                keyList = Object.keys(this);
            }

            //中止遍历
            var stopInfo;

            for (let key of keyList){
                let value = this[key];
                if (typeof value == "object"){
                    stopInfo = value.depthLoopOwnProperty(callback,depth-1,all,thisValue,initDepth+1);
                    if (stopInfo){
                        break;
                    }
                }

                stopInfo = callback.call(thisValue,key,value,this,initDepth);
                if (stopInfo){
                    break;
                }

            }


            return stopInfo;
        }


    },



    /**
     * depthLoopPropertyWithPrototype(callback,depth,thisValue,initDepth)=> stopInfo
     * 递归遍历自身包括原型的属性链中的所有可枚举的属性
     * @param callback : (key,value,obj,currDepth))=>stopInfo : any    必选； 循环遍历的回调函数； key : 当前被遍历的属性名；value : 当前被遍历的属性值；obj : 当前被遍历的属性所属的对象；currDepth : 当前遍历的深度值，从 initDepth 所表示的值开始计数；返回值 stopInfo : 表示是否中止循环，并且该值会被 depthLoopOwnProperty 函数返回，如果返回的值是真值，则终止循环；
     * @param depth ? : number    可选；默认值：-1 ,即无限深度； 要循环遍历的深度；
     * @param thisValue ? : any    可选；   callback 回调函数的this值 ；默认值：当前被遍历的属性所属的对象；
     * @param initDepth ? : number   可选；默认值：1；深度的初始值； 注意：设计该属性的主要目的是为了递归调用时记录当前传递当前的深度值的；
     * @returns stopInfo ： any   终止循环时返回的信息；
     */
    depthLoopPropertyWithPrototype:{
        enumerable: false,
        value:function (callback,depth,thisValue,initDepth) {
            if (depth == undefined){
                depth = -1;
            }

            if (depth == 0){
                return;
            }

            if (initDepth == undefined){
                initDepth = 1;
            }


            if (thisValue === undefined) {
                thisValue = this;
            }

            //中止遍历
            var stopInfo;

            for (let key in this){

                let value = this[key];
                if (typeof value == "object"){
                    stopInfo = value.depthLoopPropertyWithPrototype(callback,depth-1,thisValue,initDepth+1);
                    if (stopInfo){
                        break;
                    }
                }

                stopInfo = callback.call(thisValue,key,value,this,initDepth);
                if (stopInfo){
                    break;
                }
            }

            return stopInfo;

        }
    },


    /**
     * filterProperty(filter,thisValue)
     * 返回包含符合条件的所有属性的新对象
     * @param filter : (key,value,obj)=>boolean    必选；
     * @param thisValue ? : any   可选；默认值：当前对象； filter 函数的this 值；
     * @returns Object   返回包含符合条件的所有属性的新对象
     */
    filterProperty:{
        enumerable: false,
        value:function (filter,thisValue) {
            if (arguments.length < 2){
                thisValue = this;
            }

            return Object.entries(this).reduce((preValue,entr)=> {
                var key = entr[0];
                var value  = entr[1];
                if (filter.call(thisValue,key,value,this)){
                    preValue[key] = value;
                }
                return preValue;
            },{});
        },
    },

};


Object.defineProperties(Object.prototype, propertyDescriptors);


/**
 * 用于将所有指定的属性的值从源对象复制到目标对象。它将返回目标对象。
 * @param target : Object     目标对象。
 * @param keys : Array<String>   需要复制的属性名数组
 * @param ...sources : Object    源对象参数序列
 * @return target    返回目标对象
 */

Object.assignKeys = function (target,keys,...sources){

    if (keys) {

        let keysSourceList = sources.map(function (source) {

            return keys.reduce(function (newSource, aKey) {
                let aValue = source[aKey];

                if (aValue !== undefined) {
                    newSource[aKey] = aValue;
                }

                return newSource;

            }, {});

        });


        Object.assign(target,...keysSourceList);

    };


    return target ;
}











//兼容：开始

//Object.entries(obj)
if (!Object.entries) {
  Object.entries = function (obj) {
    var ownProps = Object.keys(obj),
      i = ownProps.length,
      resArray = new Array(i);
    while (i--) {
      var key = ownProps[i];
      resArray[i] = [key, obj[key]];
    }

    return resArray;
  };
}


//兼容：结束
